
Raphael.fn.connection = function (obj1, obj2, line, bg) {
  if (obj1.line && obj1.from && obj1.to) {
    line = obj1;
    obj1 = line.from;
    obj2 = line.to;
  }
  var bb1 = obj1.getBBox();
  var bb2;
  if(obj2 == null){
    bb2 = {
      x:0, 
      y:0, 
      height:0, 
      width:0
    };
  }else{
    bb2 = obj2.getBBox();
  }
  
  var p = [{
    x: bb1.x + bb1.width / 2, 
    y: bb1.y - 1
  },

  {
    x: bb1.x + bb1.width / 2, 
    y: bb1.y + bb1.height + 1
  },

  {
    x: bb1.x - 1, 
    y: bb1.y + bb1.height / 2
  },

  {
    x: bb1.x + bb1.width + 1, 
    y: bb1.y + bb1.height / 2
  },

  {
    x: bb2.x + bb2.width / 2, 
    y: bb2.y - 1
  },

  {
    x: bb2.x + bb2.width / 2, 
    y: bb2.y + bb2.height + 1
  },

  {
    x: bb2.x - 1, 
    y: bb2.y + bb2.height / 2
  },

  {
    x: bb2.x + bb2.width + 1, 
    y: bb2.y + bb2.height / 2
  }],
  d = {}, dis = [];
  for (var i = 0; i < 4; i++) {
    for (var j = 4; j < 8; j++) {
      var dx = Math.abs(p[i].x - p[j].x),
      dy = Math.abs(p[i].y - p[j].y);
      if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) {
        dis.push(dx + dy);
        d[dis[dis.length - 1]] = [i, j];
      }
    }
  }
  if (dis.length == 0) {
    var res = [0, 4];
  } else {
    res = d[Math.min.apply(Math, dis)];
  }
  var x1 = p[res[0]].x,
  y1 = p[res[0]].y,
  x4 = p[res[1]].x,
  y4 = p[res[1]].y;
  dx = Math.max(Math.abs(x1 - x4) / 2, 10);
  dy = Math.max(Math.abs(y1 - y4) / 2, 10);
  var x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3),
  y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3),
  x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3),
  y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3);
  var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(",");
  
  if (line && line.line) {
    line.bg && line.bg.attr({
      path: path
    });
    line.line.attr({
      path: path
    });
    var pt = line.line.getPointAtLength(line.line.getTotalLength() - 5);
    //    test4 = r.arrow(pt.x, pt.y, x4, y4, 5);
    var angle = Math.atan2(pt.x-x4,y4-pt.y);
    angle = (angle / (2 * Math.PI)) * 360;
    line.arrow.remove();
    line.arrow = this.path("M" + x4 + " " + y4 + " L" + (x4 - 5) + " " + (y4 - 5) + " L" + (x4 - 5) + " " + (y4 + 5) + " L" + x4 + " " + y4 ).attr("fill","black").rotate((90+angle),x4,y4);
    return line;
  } else {
    var color = typeof line == "string" ? line : "#000";
    
    var w = this.path(path).attr({
      stroke: color, 
      fill: "none"
    });
    
    var pt2 = w.getPointAtLength(w.getTotalLength() - 5);
    //    test4 = r.arrow(pt.x, pt.y, x4, y4, 5);
    var angle2 = Math.atan2(pt2.x-x4,y4-pt2.y);
    angle = (angle2 / (2 * Math.PI)) * 360;
    var ar = this.path("M" + x4 + " " + y4 + " L" + (x4 - 5) + " " + (y4 - 5) + " L" + (x4 - 5) + " " + (y4 + 5) + " L" + x4 + " " + y4 ).attr("fill","black").rotate((90+angle),x4,y4);
    
    return {
      bg: bg && bg.split && this.path(path).attr({
        stroke: bg.split("|")[0], 
        fill: "none", 
        "stroke-width": bg.split("|")[1] || 3
      }),
      line: w,
      from: obj1,
      to: obj2,
      arrow:ar
    };
  }
};

var vars = {};

function Start(x, y){
  var text = r.text(x, y, "Start");
  var shape = r.ellipse(x, y, 20, 20);
  shape.attr({
    fill: '#ffffff', 
    stroke: 'gray', 
    "fill-opacity": 0, 
    "stroke-width": 2
  });
  shape.drag(move, dragStart, dragEnd);
  shape.click(click);
  shape._text = text;
  shape._connection = null;
  shape._rconnections = [];
  shape._interpret = function(){
    vars = {};
    console.log("start");
    if(shape._connection){
      shape._connection.to._interpret();
    }
  };
  this.shape = shape;
}

function End(x, y){
  var text = r.text(x, y, "End");
  var shape = r.ellipse(x, y, 20, 20);
  shape.attr({
    fill: '#ffffff', 
    stroke: 'gray', 
    "fill-opacity": 0, 
    "stroke-width": 2
  });
  shape.drag(move, dragStart, dragEnd);
  shape.click(click);
  shape._text = text;
  shape._connection = null;
  shape._rconnections = [];
  shape._type = "end";
  shape._interpret = function(){
    console.log("end");
  };
  this.shape = shape;
}

function Input(x, y){
  var varName = prompt("Store into variable:");
  var shape = r.rect(x, y, 100, 40, 5);
  var text = r.text(x + (shape.attr('width')/2), y + (shape.attr('height')/2), "input into " + varName);
  shape.attr({
    fill: '#ffffff', 
    stroke: 'gray', 
    "fill-opacity": 0, 
    "stroke-width": 2
  });
  shape.drag(move, dragStart, dragEnd);
  shape.click(click);
  shape.dblclick(makeDblClick(shape, text));
  shape._text = text;
  shape._connection = null;
  shape._rconnections = [];
  shape._interpret = function(){
    var value = eval(prompt(varName + ":"));
    vars[varName] = value;
    if(shape._connection){
      shape._connection.to._interpret();
    }
  };
  this.shape = shape;
}

function OAlert(x, y){
  var c = prompt("Expression:");
  var shape = r.rect(x, y, 100, 40, 5);
  var text = r.text(x + (shape.attr('width')/2), y + (shape.attr('height')/2), "alert " + c);
  shape.attr({
    fill: '#ffffff', 
    stroke: 'gray', 
    "fill-opacity": 0, 
    "stroke-width": 2
  });
  shape.drag(move, dragStart, dragEnd);
  shape.click(click);
  shape.dblclick(makeDblClick(shape, text));
  shape._text = text;
  shape._connection = null;
  shape._rconnections = [];
  shape._interpret = function(){
    alert(eval(c));
    if(shape._connection){
      shape._connection.to._interpret();
    }
  };
  this.shape = shape;
}

function OPrint(x, y){
  var c = prompt("Expression");
  var shape = r.rect(x, y, 100, 40, 5);
  var text = r.text(x + (shape.attr('width')/2), y + (shape.attr('height')/2), "print " + c);
  shape.attr({
    fill: '#ffffff', 
    stroke: 'gray', 
    "fill-opacity": 0, 
    "stroke-width": 2
  });
  shape.drag(move, dragStart, dragEnd);
  shape.click(click);
  shape.dblclick(makeDblClick(shape, text));
  shape._text = text;
  shape._connection = null;
  shape._rconnections = [];
  shape._interpret = function(){
    console.log(eval(c));
    if(shape._connection){
      shape._connection.to._interpret();
    }
  };
  this.shape = shape;
}

function VariableDecl(x, y){
  var n = prompt("Variable name:");
  var v = eval(prompt("Initial value:"));
  var shape = r.rect(x, y, 100, 40, 5);
  var text = r.text(x + (shape.attr('width')/2), y + (shape.attr('height')/2), "var " + n + " = "+ v);
  shape.attr({
    fill: '#ffffff', 
    stroke: 'gray', 
    "fill-opacity": 0, 
    "stroke-width": 2
  });
  shape.drag(move, dragStart, dragEnd);
  shape.click(click);
  shape.dblclick(makeDblClick(shape, text));
  shape._text = text;
  shape._connection = null;
  shape._rconnections = [];
  shape._interpret = function(){
    vars[n] = v;
    if(shape._connection){
      shape._connection.to._interpret();
    }
  };
  this.shape = shape;
}

function Assignment(x, y){
  var n = prompt("Variable name:");
  var exp = prompt("Expression:");
  var shape = r.rect(x, y, 100, 40, 5);
  var text = r.text(x + (shape.attr('width')/2), y + (shape.attr('height')/2), n + " = "+ exp);
  shape.attr({
    fill: '#ffffff', 
    stroke: 'gray', 
    "fill-opacity": 0, 
    "stroke-width": 2
  });
  shape.drag(move, dragStart, dragEnd);
  shape.click(click);
  shape.dblclick(makeDblClick(shape, text));
  shape._text = text;
  shape._connection = null;
  shape._rconnections = [];
  shape._interpret = function(){
    vars[n] = eval(exp);
    if(shape._connection){
      shape._connection.to._interpret();
    }
  };
  this.shape = shape;
}

function Condition(x, y){
  var exp = prompt("Condition:");
  var shape = r.rect(x, y, 100, 40, 5);
  var text = r.text(x + (shape.attr('width')/2), y + (shape.attr('height')/2), "if "+ exp);
  shape.attr({
    fill: '#ffffff', 
    stroke: 'gray', 
    "fill-opacity": 0, 
    "stroke-width": 2
  });
  shape.drag(move, dragStart, dragEnd);
  shape.click(click);
  shape.dblclick(makeDblClick(shape, text));
  shape._text = text;
  shape._connection = -1;
  shape._connection0 = null;
  shape._connection1 = null;
  shape._rconnections = [];
  shape._interpret = function(){
    if(eval(exp) == true){
      if(shape._connection0){
        shape._connection0.to._interpret();
      }
    }else{
      if(shape._connection1){
        shape._connection1.to._interpret();
      }
    }
  };
  this.shape = shape;
}

var current = null;
var DEFAULT = 0;
var CONNECTING = 1;
var mode = DEFAULT;
var r;
var start;

var dragStart = function () {
  this.ox = this.type == "rect" ? this.attr("x") : this.attr("cx");
  this.oy = this.type == "rect" ? this.attr("y") : this.attr("cy");
  this.attr({
    cursor:'pointer'
  });
  this.animate({
    "fill-opacity": .2
  }, 500);
  
};
    
var move = function (dx, dy) {
  var att = this.type == "rect" ? {
    x: this.ox + dx, 
    y: this.oy + dy
  } : {
    cx: this.ox + dx, 
    cy: this.oy + dy
  };
  this.attr(att);
  
  if(this._text){
    var att2 = this.type == "rect" ? {
      x: this.ox + (this.attr('width')/2) +  dx, 
      y: this.oy + (this.attr('height')/2) + dy
    } : {
      x: this.ox + dx, 
      y: this.oy + dy
    };
    this._text.attr(att2);
  }
  if(this._connection === -1){
    if(this._connection0){
      r.connection(this._connection0);
    }
    if(this._connection1){
      r.connection(this._connection1);
    }
  }else{
    if(this._connection){
      r.connection(this._connection);
    }
  }
  if(this._rconnections){
    var rcons = this._rconnections;
    for(var i = 0; i < rcons.length; i++){
      r.connection(rcons[i]);
    }
  }
  r.safari();
};
        
var dragEnd = function () {
  this.attr({
    cursor:'default'
  });
  this.animate({
    "fill-opacity": 0
  }, 500);
};       

var click = function(evt){
  if(evt.ctrlKey){
    if(mode == DEFAULT){
      if(this._type && this._type == 'end'){
        mode = DEFAULT;
      }else{
        current = this;
        mode = CONNECTING;
      }
      
    }else{
      if(mode == CONNECTING){
        if(this !== current){
          if(current._connection === -1){
            if(current._connection0){
              current._connection0.to = this;
              r.connection(current._connection0);
            }else{
              current._connection0 = r.connection(current, this, "green");
            }
            this._rconnections.push(current._connection0);
          }else{
            if(current._connection){
              current._connection.to = this;
              r.connection(current._connection);
            }else{
              current._connection = r.connection(current, this, "#000");
            }
            this._rconnections.push(current._connection);
          }
          current = null;
          mode = DEFAULT;
        }
      }
    }
  }else if(evt.altKey){
    if(mode == DEFAULT){
      current = this;
      mode = CONNECTING;
    }else{
      if(mode == CONNECTING){
        if(this !== current){
          if(current._connection === -1){
            if(current._connection1){
              current._connection1.to = this;
              r.connection(current._connection1);
            }else{
              current._connection1 = r.connection(current, this, "red");
            }
          }
          this._rconnections.push(current._connection1);
          current = null;
          mode = DEFAULT;
        }
      }
    }
  }else{
    mode = DEFAULT;
  }
};

var makeDblClick = function(shape, text){
  return function(){
    if(shape._connection == -1){
      if(shape._connection0){
        shape._connection0.line.remove();
        shape._connection0.arrow.remove();
      }
      if(shape._connection1){
        shape._connection1.arrow.remove();
        shape._connection1.line.remove();
      }
    }else{
      if(shape._connection){
        shape._connection.line.remove();
        shape._connection.arrow.remove();
      }
    }
    shape.remove();
    text.remove();
  };
}

$(document).ready(function(){
  r = Raphael("holder", 640, 480);
  
  $('#create-vardecl').click(function(evt){
    new VariableDecl(150,150);
  });
  
  $('#create-assign').click(function(evt){
    new Assignment(260,260);
  });
  
  $('#create-input').click(function(evt){
    new Input(240,240);
  });
  
  $('#create-print').click(function(evt){
    new OPrint(220,220);
  });
  
  $('#create-alert').click(function(evt){
    new OAlert(220,220);
  });
  
  $('#create-condition').click(function(evt){
    new Condition(150,150);
  });
  
  $('#interpret').click(function(evt){
    execute(start);
  });
  
  start = new Start(30, 30);
  new End(600, 450);
});

function execute(start){
  start.shape._interpret();
}